#include <linux/module.h>	/* Needed by all modules */
#include <linux/kernel.h>	/* Needed for KERN_INFO */
#include <linux/init.h> 	/* Needed for the macros */
#include <linux/usb.h>
#include <linux/mutex.h>
#include <linux/fs.h>		/* Needed for file opetation */
#include <linux/slab.h>
#include <linux/mutex.h>


#define DRIVER_AUTHOR "Kruta"
#define DRIVER_DESC "USB STM32-based LCD module driver"

#define DEVICE_VENDOR_ID	0x0483
#define DEVICE_PRODUCT_ID	0xFF00
#define LCD_MINOR_BASE		0
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
#define TOTAL_BUFFER_SIZE	128

static atomic_t DeviceFree = ATOMIC_INIT(1);

static int LCDProbe(struct usb_interface *interface, const struct usb_device_id *id);
static void LCDDisconnect(struct usb_interface *interface);

static int LCDOpen(struct inode *inode, struct file *filp);
static int LCDrelease(struct inode *inode, struct file *file);
static ssize_t LCDwrite(struct file *filp, const char __user *user_buf, size_t count, loff_t *ppos);

static struct file_operations LCD_fops = {
	.owner = THIS_MODULE,
	.write = LCDwrite,
	.open = LCDOpen,
	.release = LCDrelease,
};

struct usblcd
{
	struct usb_device *udev;
	struct usb_interface *interface;
	struct mutex io_mutex;
	unsigned char minor;
	
	struct usb_endpoint_descriptor *bulk_out_ep;
	unsigned int bulk_out_packet_size;
	unsigned char *videobuffer;
};

static struct usb_device_id lcd_table[] = {
	{USB_DEVICE(DEVICE_VENDOR_ID, DEVICE_PRODUCT_ID)},
	{}
};

struct usb_driver usblcd_driver = {
//	.owner = THIS_MODULE,
	.name = "usblcd",
	.probe = LCDProbe,
	.disconnect = LCDDisconnect,
	.id_table = lcd_table,
};

static struct usb_class_driver usblcd_class = {
	.name = "lcd%d",
	.fops = &LCD_fops,
	.minor_base = LCD_MINOR_BASE,		
};

static int LCDOpen(struct inode *inode, struct file *filp)
{
	struct usblcd *dev;
	struct usb_interface *interface;
	int subminor;
	
	filp->private_data = NULL;
	
	if(!atomic_dec_and_test(&DeviceFree))
	{
		atomic_inc(&DeviceFree);
		return -EBUSY;
	}
	
	subminor = iminor(inode);
	interface = usb_find_interface(&usblcd_driver, subminor);
	if(!interface)
	{
		printk(KERN_ERR "usblcd driver error, can't find device for minor %d\n", subminor);
		return -ENODEV;
	}
	
	dev = usb_get_intfdata(interface);
	if(!dev)
	{
		return -ENODEV;
	}
	
	mutex_lock(&dev->io_mutex);
	
	if(!dev->interface)
	{
		mutex_unlock(&dev->io_mutex);
		return -ENODEV;
	}
	
	filp->private_data = dev;
	dev_info(&interface->dev, "usblcd: opened successfuly");
	
	mutex_unlock(&dev->io_mutex);
	return 0;
}

static int LCDrelease(struct inode *inode, struct file *file)
{
	atomic_inc(&DeviceFree);
	
	return 0;
}

static ssize_t LCDwrite(struct file *filp, const char __user *user_buf, size_t count, loff_t *ppos)
{	
	struct usblcd *dev;
	struct usb_interface *interface;
	int writtenCount;
	int retval,ret_cp;
	
	dev = filp->private_data;
	if(!dev)
	{
		printk(KERN_ERR "usblcd driver error, no device found!\n");
		return -ENODEV;
	}
	
	mutex_lock(&dev->io_mutex);
	interface = dev->interface;
	if(!interface)
	{
		printk(KERN_ERR "usblcd driver error, no device found!\n");
		return -ENODEV;
	}
		
	/* writtenCount = MIN(count, dev->bulk_out_packet_size); */
	writtenCount = count;
	
	ret_cp = copy_from_user(dev->videobuffer, user_buf, writtenCount);
	if(ret_cp)
	{
		mutex_unlock(&dev->io_mutex);
		printk("copy from user error - %d!\n", ret_cp);
		return -EFAULT;
	}
	
	retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, (dev->bulk_out_ep)->bEndpointAddress),\
			 dev->videobuffer, writtenCount, &writtenCount, HZ*5);
	
	if(retval)
	{
		mutex_unlock(&dev->io_mutex);
		return -EIO;
	}
	
	mutex_unlock(&dev->io_mutex);
	return retval;
}

static int LCDProbe(struct usb_interface *interface, const struct usb_device_id *id)
{
	struct usblcd *dev;
	struct usb_host_interface *iface_desc;
	struct usb_endpoint_descriptor *endpoint;
	
	int retval = -ENODEV;
	int i;
	
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if(!dev)
	{
		dev_err(&interface->dev, "Out of memory");
		retval = -ENOMEM;
		return retval;
	}
	
	dev->udev = interface_to_usbdev(interface);
	mutex_init(&dev->io_mutex);
	
	dev->interface = interface;
	
	iface_desc = interface->cur_altsetting;
	for(i = 0; i< iface_desc->desc.bNumEndpoints; i++)
	{
		endpoint = &iface_desc->endpoint[i].desc;
		
		if(usb_endpoint_is_bulk_out(endpoint))
		{
			dev->bulk_out_ep = endpoint;
			dev->bulk_out_packet_size = le16_to_cpu(endpoint->wMaxPacketSize);
			
			printk("dev->bulk_out_ep = 0x%x.\n", (dev->bulk_out_ep)->bEndpointAddress);
			/* printk("bulk out packat size = 0x%x.\n", endpoint->wMaxPacketSize); */
			printk("le 16 to cpu(bulk out packat size) = 0x%x.\n", le16_to_cpu(endpoint->wMaxPacketSize));
						
			break;
		}
	}
  	
	if(!dev->bulk_out_ep)
	{
		dev_err(&interface->dev, "Can not find bulk-out endpoint!\n");
		retval = -ENOMEM;
		kfree(dev);
		return retval;
	}
	
	dev->videobuffer = kmalloc(TOTAL_BUFFER_SIZE, GFP_KERNEL);
	if(!dev->videobuffer)
	{
		dev_err(&interface->dev, "Out of memory!\n");
		kfree(dev);
		return -ENOMEM;
	}
	
	usb_set_intfdata(interface, dev);
	
	retval = usb_register_dev(interface, &usblcd_class);
	if(retval)
	{
		dev_err(&interface->dev, "Not able to get a minor for this device.");
		usb_set_intfdata(interface, NULL);
		kfree(dev->videobuffer);
		kfree(dev);
		return retval;
	}
	
	dev->minor = interface->minor;
	dev_info(&interface->dev, "USB STM32-based LCD module connected as lcd%d", dev->minor - LCD_MINOR_BASE);
	return 0;
}

static void LCDDisconnect(struct usb_interface *interface)
{
	struct usblcd *dev;
	int minor;
	
	dev = usb_get_intfdata(interface);
	minor = dev->minor;
	usb_set_intfdata(interface, NULL);
	usb_deregister_dev(interface, &usblcd_class);
	
	mutex_lock(&dev->io_mutex);
	dev->interface = NULL;
	mutex_unlock(&dev->io_mutex);
	
	kfree(dev->videobuffer);
	kfree(dev);
	dev_info(&interface->dev, "USB STM32-based module lcd%d disconnected.\n", minor - LCD_MINOR_BASE);
	
}

MODULE_LICENSE("GPL");

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

MODULE_DEVICE_TABLE(usb, lcd_table);

module_usb_driver(usblcd_driver);
